/**
* $Id: DateConverter.java 65 2010-04-05 14:53:55Z azeckoski $
* $URL: http://reflectutils.googlecode.com/svn/trunk/src/main/java/org/azeckoski/reflectutils/converters/DateConverter.java $
* DateConverter.java - genericdao - Sep 8, 2008 12:52:47 PM - azeckoski
**************************************************************************
* Copyright (c) 2008 Aaron Zeckoski
* Licensed under the Apache License, Version 2.0
*
* A copy of the Apache License has been included in this
* distribution and is available at: http://www.apache.org/licenses/LICENSE-2.0.txt
*
* Aaron Zeckoski (azeckoski @ gmail.com) (aaronz @ vt.edu) (aaron @ caret.cam.ac.uk)
*/
package org.azeckoski.reflectutils.converters;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import org.azeckoski.reflectutils.ConstructorUtils;
import org.azeckoski.reflectutils.converters.api.Converter;
/**
* Handles conversions from objects into {@link Date},
* generally the best way to handle this is to simply do conversions based on longs<br/>
* Allows control over the conversion formats which are used by settings the patterns or the formats<br/>
* <br/>
* Based on code from commons bean utils but updated for generics and removed complexity and (mis)usage of Exception<br/>
*
* @author Aaron Zeckoski (azeckoski @ gmail.com)
*/
public class DateConverter extends BaseDateFormatHolder implements Converter<Date> {
public Date convert(Object value) {
return DateConverter.convertToType(Date.class, value, getDateFormats());
}
// CONSTRUCTORS
public DateConverter() {
super();
}
public DateConverter(String[] patterns) {
super(patterns);
}
public DateConverter(DateFormat[] formats) {
super(formats);
}
/**
* Convert an object to one of the following date types<br/>
* <ul>
* <li><code>java.util.Date</code></li>
* <li><code>java.util.Calendar</code></li>
* <li><code>java.sql.Date</code></li>
* <li><code>java.sql.Time</code></li>
* <li><code>java.sql.Timestamp</code></li>
* </ul>
*
* @param <T>
* @param targetType the type to convert to (should be one of the given date types)
* @param value any object
* @return the converted value
*/
public static <T> T convertToType(Class<T> targetType, Object value) {
return convertToType(targetType, value, null);
}
/**
* @param <T>
* @param targetType the type to convert to (should be one of the given date types)
* @param value any object
* @param dateFormats include date formats to use during the string -> date conversion
* @return the converted value
*/
@SuppressWarnings("unchecked")
public static <T> T convertToType(Class<T> targetType, Object value, DateFormat[] dateFormats) {
if (value == null) {
throw new IllegalArgumentException("value to convert cannot be null");
}
// Handle java.sql.Timestamp
if (value instanceof java.sql.Timestamp) {
long timeInMillis = ((Timestamp)value).getTime();
// ---------------------- JDK 1.3 Fix ----------------------
// N.B. Prior to JDK 1.4 the Timestamp's getTime() method
// didn't include the milliseconds. The following code
// ensures it works consistently accross JDK versions
// java.sql.Timestamp timestamp = (java.sql.Timestamp)value;
// long timeInMillis = ((timestamp.getTime() / 1000) * 1000);
// timeInMillis += timestamp.getNanos() / 1000000;
// ---------------------- JDK 1.3 Fix ----------------------
return (T) toDate(targetType, timeInMillis);
}
// Handle Date (includes java.sql.Date & java.sql.Time)
if (value instanceof Date) {
long time = ((Date)value).getTime();
return (T) toDate(targetType, time);
}
// Handle Calendar
if (value instanceof Calendar) {
long time = ((Calendar)value).getTimeInMillis();
return (T) toDate(targetType, time);
}
// Handle numbers
if (value instanceof Number) {
long num = ((Number)value).longValue();
return (T) toDate(targetType, num);
}
// Convert all other types to String & handle
String stringValue = value.toString().trim();
if (stringValue.length() == 0) {
Object defaultValue = ConstructorUtils.getDefaultValue(targetType);
if (defaultValue == null) {
throw new UnsupportedOperationException("Date convert failure: Could not convert empty string to target ("+targetType+") or get a default value for it");
}
return (T) defaultValue;
} else {
// try to get the long value out of the string
try {
long num = new Long(stringValue);
if (num > 30000000l) {
// must be a UTC time code, also, we only lost a week since 1970 so whatever
return (T) toDate(targetType, num);
} else if (num > 10000l) {
// probably is a date like: YYYYMMDD so convert it
int date = (int) (num % 100);
int remain = (int) (num / 100);
int month = remain % 100;
int year = remain / 100;
if (date <= 31 && month <= 12) {
if (month > 0) { month -= 1; }
Calendar calendar = Calendar.getInstance();
calendar.clear();
calendar.set(year, month, date);
long time = calendar.getTimeInMillis();
return (T) toDate(targetType, time);
}
}
} catch (NumberFormatException e) {
// ok, not a long probably so try parsing
}
}
// Parse the Date/Time
Calendar calendar = parse(stringValue, dateFormats);
if (calendar != null) {
long time = ((Calendar)value).getTimeInMillis();
return (T) toDate(targetType, time);
}
// Default String conversion
return (T) toDate(targetType, stringValue);
}
/**
* Convert a long value to the specified Date type for this
* <i>Converter</i>.
* <p>
*
* This method handles conversion to the following types:
* <ul>
* <li><code>java.util.Date</code></li>
* <li><code>java.util.Calendar</code></li>
* <li><code>java.sql.Date</code></li>
* <li><code>java.sql.Time</code></li>
* <li><code>java.sql.Timestamp</code></li>
* </ul>
*
* @param type The Date type to convert to
* @param value The long value to convert.
* @return The converted date value.
*/
protected static Object toDate(Class<?> type, long value) {
// java.util.Date
if (type.equals(Date.class)) {
return new Date(value);
}
// java.sql.Date
if (type.equals(java.sql.Date.class)) {
return new java.sql.Date(value);
}
// java.sql.Time
if (type.equals(java.sql.Time.class)) {
return new java.sql.Time(value);
}
// java.sql.Timestamp
if (type.equals(java.sql.Timestamp.class)) {
return new java.sql.Timestamp(value);
}
// java.util.Calendar
if (type.equals(Calendar.class)) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(value);
// calendar.setLenient(false);
return calendar;
}
throw new UnsupportedOperationException("Date conversion failure: cannot convert long value ("+value+") to ("+type+")");
}
/**
* Default String to Date conversion.
* <p>
* This method handles conversion from a String to the following types:
* <ul>
* <li><code>java.sql.Date</code></li>
* <li><code>java.sql.Time</code></li>
* <li><code>java.sql.Timestamp</code></li>
* </ul>
* <p>
* <strong>N.B.</strong> No default String conversion
* mechanism is provided for <code>java.util.Date</code>
* and <code>java.util.Calendar</code> type.
*
* @param type The Number type to convert to
* @param value The String value to convert.
* @return The converted Number value.
*/
protected static Object toDate(Class<?> type, String value) {
// java.sql.Date
if (type.equals(java.sql.Date.class)) {
try {
return java.sql.Date.valueOf(value);
} catch (IllegalArgumentException e) {
throw new UnsupportedOperationException("Date conversion failure: " +
"String must be in JDBC format [yyyy-MM-dd] to create a java.sql.Date");
}
}
// java.sql.Time
if (type.equals(java.sql.Time.class)) {
try {
return java.sql.Time.valueOf(value);
} catch (IllegalArgumentException e) {
throw new UnsupportedOperationException("Date conversion failure: " +
"String must be in JDBC format [HH:mm:ss] to create a java.sql.Time");
}
}
// java.sql.Timestamp
if (type.equals(java.sql.Timestamp.class)) {
try {
return java.sql.Timestamp.valueOf(value);
} catch (IllegalArgumentException e) {
throw new UnsupportedOperationException("Date conversion failure: " +
"String must be in JDBC format [yyyy-MM-dd HH:mm:ss.fffffffff] " +
"to create a java.sql.Timestamp");
}
}
throw new UnsupportedOperationException("Date conversion failure: cannot convert string value ("+value+") to ("+type+")");
}
/**
* Parse a String date value using the set of patterns.
*
* @param value The String date value.
* @return The converted Date object.
* @throws Exception if an error occurs parsing the date.
*/
protected static Calendar parse(String value, DateFormat[] dateFormats) {
Calendar calendar = null;
if (dateFormats != null && dateFormats.length > 0) {
for (DateFormat format : dateFormats) {
calendar = parse(value, format);
if (calendar != null) {
break;
}
}
}
if (calendar == null) {
// just do the simple attempt now
DateFormat format = new SimpleDateFormat();
try {
Date date = format.parse(value);
if (date != null) {
calendar = format.getCalendar();
}
} catch (ParseException e) {
calendar = null;
}
}
return calendar;
}
/**
* Parse a String into a <code>Calendar</code> object using the specified <code>DateFormat</code><br/>
* Does exact matching (non-lenient) only on the given pattern<br/>
*
* @param value The String date value.
* @param format The DateFormat to parse the String value.
* @return The converted Calendar object OR null if it cannot be converted
*/
protected static Calendar parse(String value, DateFormat format) {
format.setLenient(false);
ParsePosition pos = new ParsePosition(0);
Date parsedDate = format.parse(value, pos); // ignore the result (use the Calendar)
Calendar calendar = null;
if (pos.getErrorIndex() >= 0 || pos.getIndex() != value.length() || parsedDate == null) {
calendar = null;
} else {
calendar = format.getCalendar();
}
return calendar;
}
}